/*
 * Copyright (C) 2014 Miquel Sas
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package traderlib.core.data.db;

import java.util.ArrayList;
import java.util.List;
import traderlib.core.data.entity.FieldList;
import traderlib.core.data.entity.Order;

/**
 * An SQL database view, build with a master table and relations.
 *
 * @author Miquel Sas
 */
public class View implements Comparable<Object> {

	/**
	 * The name of the table.
	 */
	private String name = null;
	/**
	 * The alias.
	 */
	private String alias = null;
	/**
	 * An optional description.
	 */
	private String description = null;
	/**
	 * The master table.
	 */
	private Table masterTable = null;
	/**
	 * The array of relations.
	 */
	private final List<Relation> relations = new ArrayList<>();

	/**
	 * The list of columns.
	 */
	private final FieldList columns = new FieldList();

	/**
	 * The array of group by columns.
	 */
	private final List<Column> groupBy = new ArrayList<>();
	/**
	 * The order.
	 */
	private Order orderBy = null;

	/**
	 * Default constructor.
	 */
	public View() {
		super();
	}

	/**
	 * Copy contructor.
	 *
	 * @param view The source view
	 */
	public View(View view) {
		super();
		this.name = view.name;
		this.alias = view.alias;
		this.description = view.description;
		this.masterTable = view.masterTable;
		this.relations.addAll(view.relations);
		this.columns.getFields().addAll(view.columns.getFields());
		this.groupBy.addAll(view.groupBy);
		this.orderBy = view.orderBy;
	}

	/**
	 * Get the name.
	 *
	 * @return The table name
	 */
	public String getName() {
		return name;
	}

	/**
	 * Gets the name to use in FROM clause.
	 *
	 * @return The appropriate name for a FROM clause.
	 */
	public String getNameFrom() {
		if (alias != null && getName() != null) {
			return getName() + " " + alias;
		}
		return getName();
	}

	/**
	 * Set the name.
	 *
	 * @param name The table name
	 */
	public void setName(String name) {
		this.name = null;
		if (name != null && name.trim().length() > 0) {
			this.name = name.trim();
		}
	}

	/**
	 * Gets the alias.
	 *
	 * @return The alias.
	 */
	public String getAlias() {
		if (alias == null) {
			return name;
		}
		return alias;
	}

	/**
	 * Sets the alias.
	 *
	 * @param alias The table alias.
	 */
	public void setAlias(String alias) {
		this.alias = null;
		if (alias != null && alias.trim().length() > 0) {
			this.alias = alias;
		}
	}

	/**
	 * Get the description.
	 *
	 * @return The description.
	 */
	public String getDescription() {
		return description;
	}

	/**
	 * Set the description.
	 *
	 * @param description The description.
	 */
	public void setDescription(String description) {
		this.description = description;
	}

	/**
	 * Get the master table.
	 *
	 * @return The kaster table.
	 */
	public Table getMasterTable() {
		return masterTable;
	}

	/**
	 * Set the master table.
	 *
	 * @param masterTable The master table.
	 */
	public void setMasterTable(Table masterTable) {
		this.masterTable = masterTable;
	}

	/**
	 * Compares this table with the argument table. Returns 0 if they are equal, -1 if this value is less than the
	 * argument, and 1 if it is greater. A table is considered to be equal to another table in the context of a
	 * <code>SELECT</code> statement.
	 *
	 * @return An int
	 * @param o The object to compare with.
	 */
	@Override
	public int compareTo(Object o) {
		View view = null;
		try {
			view = (View) o;
		} catch (ClassCastException exc) {
			throw new UnsupportedOperationException("Not comparable type: " + o.getClass().getName());
		}
		return getNameFrom().compareTo(view.getNameFrom());
	}

	/**
	 * Clear the column map, necessary when changing fields.
	 */
	public void clearColumnMap() {
		columns.clearFieldMap();
	}

	/**
	 * Add a column to this view.
	 *
	 * @param column The column to add.
	 */
	public void addColumn(Column column) {
		column.setParentView(this);
		columns.addField(column);
		clearColumnMap();
	}

	/**
	 * Returns the number of columns.
	 *
	 * @return The number of columns.
	 */
	public int getColumnCount() {
		return columns.size();
	}

	/**
	 * Get a column index by alias.
	 *
	 * @return The column index or -1 if not found.
	 * @param alias The column alias.
	 */
	public int getColumnIndex(String alias) {
		return columns.getFieldIndex(alias);
	}

	/**
	 * Returns the columns, for use in the friend class Cursor.
	 *
	 * @return The column list.
	 */
	public FieldList getColumnList() {
		return columns;
	}

	/**
	 * Get a column by index.
	 *
	 * @return The column.
	 * @param index The column index.
	 */
	public Column getColumn(int index) {
		return (Column) columns.getField(index);
	}

	/**
	 * Get a column by name.
	 *
	 * @return The column or null if not found.
	 * @param alias The column alias.
	 */
	public Column getColumn(String alias) {
		return (Column) columns.getField(alias);
	}

	/**
	 * Add a column to the group by list.
	 *
	 * @param column The column to add.
	 */
	public void addGroupBy(Column column) {
		groupBy.add(column);
	}

	/**
	 * Returns the group by column given its index in the group by list.
	 *
	 * @param index The index in the group by list.
	 * @return The column.
	 */
	public Column getGroupBy(int index) {
		return groupBy.get(index);
	}

	/**
	 * Returns the number of items in the group by list of columns.
	 *
	 * @return The number of group by columns.
	 */
	public int getGroupByCount() {
		return groupBy.size();
	}

	/**
	 * Gets the order by index.
	 *
	 * @return The order by index.
	 */
	public Order getOrderBy() {
		return orderBy;
	}

	/**
	 * Set the order by.
	 *
	 * @param orderBy The order by index.
	 */
	public void setOrderBy(Order orderBy) {
		this.orderBy = orderBy;
	}

	/**
	 * Add an order by segment.
	 *
	 * @param column The column of the order by segment.
	 * @param asc A flag indicating if the segment is ascending or descending.
	 */
	public void addOrderBy(Column column, boolean asc) {
		if (orderBy == null) {
			orderBy = new Order();
		}
		orderBy.add(column, asc);
	}

	/**
	 * Add a relation to this view.
	 *
	 * @param relation The relation to add.
	 */
	public void addRelation(Relation relation) {
		relations.add(relation);
		clearColumnMap();
	}

	/**
	 * Returns the number of relations.
	 *
	 * @return The number of relations.
	 */
	public int getRelationCount() {
		return relations.size();
	}

	/**
	 * Get a relation given its index in the list of relations.
	 *
	 * @return The relation.
	 * @param index The relation index.
	 */
	public Relation getRelation(int index) {
		return relations.get(index);
	}

	/**
	 * Returns the list of relations.
	 *
	 * @return The list of relations.
	 */
	public List<Relation> getRelations() {
		return relations;
	}

	/**
	 * Remove all columns. At the same time removes the order by order and the group by columns.
	 *
	 * @return The removed columns
	 */
	public List<Column> removeAllColumns() {
		List<Column> removedColumns = new ArrayList<>();
		for (int i = 0; i < getColumnCount(); i++) {
			removedColumns.add(getColumn(i));
		}
		columns.clear();
		orderBy = null;
		groupBy.clear();
		clearColumnMap();
		return removedColumns;
	}

	/**
	 * Removes the column at the give index. At the same time removes the order by order and the group by columns.
	 *
	 * @param index The index of the column.
	 * @return The removed column
	 */
	public Column removeColumn(int index) {
		Column removedColumn = (Column) columns.removeField(index);
		orderBy = null;
		groupBy.clear();
		clearColumnMap();
		return removedColumn;
	}

	/**
	 * Removes all relations.
	 *
	 * @return The removed relations.
	 */
	public List<Relation> removeAllRelations() {
		List<Relation> removedRelations = new ArrayList<>();
		removedRelations.addAll(relations);
		return removedRelations;
	}

	/**
	 * Removes the relation at the given index.
	 *
	 * @param index The relation position
	 * @return The removed relation
	 */
	public Relation removeRelation(int index) {
		return relations.remove(index);
	}

	/**
	 * Removes all the group by columns.
	 *
	 * @return The removed group by columns
	 */
	public List<Column> removeAllGroupByColumns() {
		List<Column> removedColumns = new ArrayList<>();
		removedColumns.addAll(groupBy);
		groupBy.clear();
		return removedColumns;
	}

	/**
	 * Removes a group by column given its position.
	 *
	 * @param index The column position
	 * @return The removed group by column
	 */
	public Column removeGoupByColumn(int index) {
		return groupBy.remove(index);
	}

	/**
	 * Gets the list of tables, where the first is this view.
	 *
	 * @return The array of table definitions.
	 */
	public List<Table> getAllTables() {
		List<Table> list = new ArrayList<>();
		list.add(getMasterTable());
		for (Relation relation : relations) {
			if (!list.contains(relation.getLocalTable())) {
				list.add(relation.getLocalTable());
			}
			if (!list.contains(relation.getForeignTable())) {
				list.add(relation.getForeignTable());
			}
		}
		return list;
	}
}
